package com.lu.rpc.proxy.core.clients;

import cn.hutool.core.convert.BasicType;
import cn.hutool.core.io.resource.BytesResource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.*;
import cn.hutool.http.*;
import cn.hutool.json.JSONUtil;
import com.lu.rpc.proxy.core.annotations.RpcApiRequest;
import com.lu.rpc.proxy.core.annotations.RpcClient;
import com.lu.rpc.proxy.core.clients.domain.response.RpcResponse;
import com.lu.rpc.proxy.core.config.RpcApplicationContext;
import com.lu.rpc.proxy.core.config.RpcClientProperties;
import com.lu.rpc.proxy.core.constants.enums.RpcProtocolEnum;
import javassist.*;
import javassist.bytecode.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 描述
 *
 * @author: zhanglu
 * @date: 2023/7/6 20:56
 * @modify: 修改人信息, 修改内容 2023/7/6 20:56
 */
@Slf4j
public class RpcInvocationHandler implements InvocationHandler {

    private Class<?> type;

    public RpcInvocationHandler(Class<?> type) {
        this.type = type;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Class<?> returnType = method.getReturnType();
        RpcApiRequest rpcApiRequest = method.getAnnotation(RpcApiRequest.class);
        RpcClient rpcClient = method.getDeclaringClass().getAnnotation(RpcClient.class);
        //执行rpc调用
        HttpResponse execute = null;
        try {
            String[] methodParams = getMethodParams(method);
            RpcClientProperties properties = RpcApplicationContext.getApplicationContext().getBean(RpcClientProperties.class);
            if (Objects.equals(RpcProtocolEnum.HTTP, properties.getProtocol())) {
                UrlBuilder urlBuilder = UrlBuilder.of(rpcClient.host(), CharsetUtil.CHARSET_UTF_8)
                        .setQuery(new UrlQuery())
                        .addPath(rpcClient.path()).addPath(rpcApiRequest.value());
                HttpRequest request = new HttpRequest(urlBuilder).method(rpcApiRequest.method());
                Map<String, Object> body = new LinkedHashMap<>();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Annotation[] parameterAnnotation = parameterAnnotations[i];
                    if (args[i] == null) {
                        continue;
                    }
                    if (Objects.equals(RequestParam.class, parameterAnnotation[0].annotationType())) {
                        if (args[i] instanceof MultipartFile) {
                            MultipartFile multipartFile = (MultipartFile) args[i];
                            request.form(methodParams[i], new BytesResource(multipartFile.getBytes(), multipartFile.getOriginalFilename()));
                        } else {
                            urlBuilder.addQuery(methodParams[i], args[i].toString());
                        }
                    } else if (Objects.equals(RequestBody.class, parameterAnnotation[0].annotationType())) {
                        request.header(Header.CONTENT_TYPE, ContentType.JSON.getValue());
                        if (args[i] instanceof String || ClassUtil.isBasicType(returnType)) {
                            request.body(args[i].toString());
                        } else {
                            body.put(methodParams[i], args[i]);
                        }
                    } else if (Objects.equals(PathVariable.class, parameterAnnotation[0].annotationType())) {
                        List<String> segments = urlBuilder.getPath().getSegments();
                        String segment = segments.get(urlBuilder.getPath().getSegments().size() - 1);
                        segments.remove(urlBuilder.getPath().getSegments().size() - 1);
                        segments.add(segment.replace("{" + methodParams[i] + "}", args[i].toString()));
                    } else if (Objects.equals(RequestHeader.class, parameterAnnotation[0].annotationType())) {
                        request.header(methodParams[i], args[i].toString());
                    }
                }
                if (ObjectUtil.isNotEmpty(body)) {
                    request.body(JSONUtil.toJsonStr(body));
                }

                execute = request.execute();
            }
            RpcResponse response = new RpcResponse();
            response.setStatus(execute.getStatus());
            response.setOk(execute.isOk());
            HttpResponse finalExecute = execute;
            Assert.isTrue(response.isOk(), () -> new RpcExeception(finalExecute.body(), response));
            //结果处理
            return returnProcess(returnType, execute);
        } catch (RpcExeception e) {
            return fallback(method, args, e.getResponse());
        } catch (Exception e) {
            return fallback(method, args, new RpcResponse().setErrorMsg(e.getMessage()).setOk(false));
        } finally {
            if (ObjectUtil.isNotEmpty(execute)) {
                execute.close();
            }
        }
    }

    /**
     * 返回结果处理
     * @param returnType    返回类型
     * @param execute       响应
     * @return              业务结果
     */
    private Object returnProcess(Class<?> returnType, HttpResponse execute){
        Object returnObj = null;
        if(Objects.equals(Void.class, returnType)
                || Objects.equals(void.class, returnType)){
            return returnObj;
        }
        if (Objects.equals(String.class, returnType)) {
            returnObj = execute.body();
        } else if (ClassUtil.isBasicType(returnType)) {
            returnObj = ReflectUtil.newInstance(BasicType.wrap(returnType), execute.body());
        } else if (returnType.isArray()) {
            //数组
            if(Objects.equals(Byte[].class, returnType) || Objects.equals(byte[].class, returnType)){
                returnObj = execute.bodyBytes();
            }else {
                //数组类型
                Class<?> componentType = returnType.getComponentType();
                //数组转list
                Collection objects = JSONUtil.toList(execute.body(), componentType);
                //list转数组
                returnObj = ArrayUtil.toArray(objects, componentType);
            }
        } else {
            returnObj = JSONUtil.toBean(execute.body(), returnType, true);
        }
        return returnObj;
    }

    /**
     * 通过类名 方法名获取方法中的参数名
     *
     * @param method 方法
     * @return
     */
    public static String[] getMethodParams(Method method) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(method.getDeclaringClass().getName());
        CtMethod cm = cc.getDeclaredMethod(method.getName());
        MethodInfo methodInfo = cm.getMethodInfo();
        if (cc.isInterface()) {
            //获取参数的个数
            String[] paramNames = new String[cm.getParameterTypes().length];
            MethodParametersAttribute methodParameters = (MethodParametersAttribute) methodInfo.getAttribute(MethodParametersAttribute.tag);
            for (int i = 0; i < paramNames.length; i++) {
                paramNames[i] = methodParameters.getConstPool().getUtf8Info(ByteArray.readU16bit(methodParameters.get(), i * 4 + 1));
            }
            return paramNames;
        } else {
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            String[] paramNames = new String[cm.getParameterTypes().length];
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr != null) {
                int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
                for (int i = 0; i < paramNames.length; i++) {
                    paramNames[i] = attr.variableName(i + pos);
                }
            }
            return paramNames;
        }
    }

    public Object fallback2() {
        return null;
    }

    public Object fallback(Method method, Object[] args, RpcResponse response) {
        try {
            //服务熔断与降级处理
            Class<?>[] parameterTypes = method.getParameterTypes();
            RpcClient rpcClient = method.getDeclaringClass().getAnnotation(RpcClient.class);
            Class<?> fallbackClass = rpcClient.fallback();
            Object fallback = fallbackClass.newInstance();
            RpcApplicationContext.RPC_RESPONSE_THREAD_LOCAL.set(response);
            return fallbackClass.getMethod(method.getName(), parameterTypes).invoke(fallback, args);
        } catch (Exception e) {
            throw new RpcExeception(e.getMessage(), response);
        } finally {
            RpcApplicationContext.RPC_RESPONSE_THREAD_LOCAL.remove();
        }
    }

}
